﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using FriendsNearby.Services.Facebook;
using Moq;
using System.Linq.Expressions;
using Facebook.Service;

namespace FriendsNearby.Services.Tests
{
    [TestFixture]
    public class FacebookSocialNetworkServiceFixture
    {
        [Test]
        public void ShouldRemoveFriendsWithAppInstalledButNoUserRegistry()
        {
            var guids = new Guid[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };

            var dataAccess = new Mock<IDataAccess>();
            var userService = new Mock<IUsersService>();
            var fbFactory = new Mock<IFacebookServiceFactory>();
            var service_0 = new Mock<IFacebookService>();
                        
            #region dataAccess: i!= 2, UserRegistry { UserID = guids[i], FacebookUserId = 100 + i }
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = guids[2] }))))
                .Returns((UserRegistry) null);

            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = guids[0] }))))
                .Returns(new UserRegistry { UserID = guids[0], FacebookUserId = "100" });
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = guids[1] }))))
                .Returns(new UserRegistry { UserID = guids[1], FacebookUserId = "101" });
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = guids[3] }))))
                .Returns(new UserRegistry { UserID = guids[3], FacebookUserId = "103" });

            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { FacebookUserId = "102" }))))
                .Returns((UserRegistry)null);
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { FacebookUserId = "100" }))))
                .Returns(new UserRegistry { UserID = guids[0], FacebookUserId = "100" });
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { FacebookUserId = "101" }))))
                .Returns(new UserRegistry { UserID = guids[1], FacebookUserId = "101" });
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { FacebookUserId = "103" }))))
                .Returns(new UserRegistry { UserID = guids[3], FacebookUserId = "103" });
            #endregion            
            dataAccess.Expect(x => x.GetUserSessionKey("100")).Returns("999");
            fbFactory.Expect(x => x.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), "100")).Returns(service_0.Object);
            service_0.Expect(x => x.IsValid()).Returns(true);
            service_0.Expect(x => x.GetFriends(It.Is<FriendFilter>(f => f == FriendFilter.AppUsers))).Returns(new string[] { "101", "102", "103" });

            var service = new FacebookSocialNetworkService(dataAccess.Object, userService.Object, fbFactory.Object);
            var actualFriends = service.GetFriends(guids[0]);

            Assert.AreEqual(2, actualFriends.Count());
            Assert.IsTrue(actualFriends.Contains(guids[1]));
            Assert.IsTrue(actualFriends.Contains(guids[3]));
        }

        [Test]
        public void ShouldRegisterDisplayNameInRegistration()
        {
            var userService = new Mock<IUsersService>();
            var dataAccess = new Mock<IDataAccess>();
            var fbFactory = new Mock<IFacebookServiceFactory>();
            var service_0 = new Mock<IFacebookService>();
            Guid guid = Guid.NewGuid();
            var facebookUserID = "123";
            var sessionKey = "00000";
            var userName = "John Doe";

            fbFactory.Expect(x => x
                .Create(It.IsAny<string>(), It.IsAny<string>(), sessionKey, facebookUserID))
                .Returns(service_0.Object);
            service_0.Expect(x => x.UserName).Returns(userName);
            userService.Expect(x => x.RegisterUser()).Returns(guid);
            #region Expect user 'facebookUserID' is first time registration
            userService.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { FacebookUserId = facebookUserID }))))
                .Returns((UserRegistry)null);
            #endregion

            bool userStored = false;
            dataAccess.Expect(x => x.AddFacebookIdentity(guid, facebookUserID, sessionKey, userName)).Callback(() => userStored = true);

            var service = new FacebookSocialNetworkService(dataAccess.Object, userService.Object, fbFactory.Object);
            service.RegisterFacebookUser(facebookUserID, sessionKey);

            Assert.IsTrue(userStored);
        }

        [Test]
        public void ShouldRegisterDisplayNameInEnsureUser()
        {
            var userService = new Mock<IUsersService>();
            var dataAccess = new Mock<IDataAccess>();
            var fbFactory = new Mock<IFacebookServiceFactory>();
            var service_0 = new Mock<IFacebookService>();
            Guid guid = Guid.NewGuid();
            var facebookUserID = "123";
            var sessionKey = "00000";
            var userName = "John Doe";

            fbFactory.Expect(x => x
                .Create(It.IsAny<string>(), It.IsAny<string>(), sessionKey, facebookUserID))
                .Returns(service_0.Object);
            service_0.Expect(x => x.UserName).Returns(userName);
            userService.Expect(x => x.RegisterUser()).Returns(guid);
            #region Expect user 'facebookUserID' is first time registration
            userService.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { FacebookUserId = facebookUserID }))))
                .Returns(new UserRegistry { UserID = guid, FacebookUserId = facebookUserID, DisplayName = "" });
            #endregion

            bool userStored = false;
            dataAccess.Expect(x => x.EnsureUserExists(facebookUserID, sessionKey, userName)).Callback(() => userStored = true);

            var service = new FacebookSocialNetworkService(dataAccess.Object, userService.Object, fbFactory.Object);
            service.EnsureFacebookUserExists(facebookUserID, sessionKey);

            Assert.IsTrue(userStored);
        }

        [Test]
        public void ShouldSendIntrodutionNotificationWithFacebookUserIDs()
        {
            var senderId = Guid.NewGuid();
            var mutualId = Guid.NewGuid();
            var otherId = Guid.NewGuid();
            var senderFbId = "1001";
            var mutualFbId = "2002";
            var otherFbId = "3003";

            var userService = new Mock<IUsersService>();
            var dataAccess = new Mock<IDataAccess>(MockBehavior.Loose);
            #region Expect QueryUserRegistry per UserId returns registry with FacebookId
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = senderId }))))
                .Returns(new UserRegistry { UserID = senderId, FacebookUserId = senderFbId });
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = mutualId }))))
                .Returns(new UserRegistry { UserID = mutualId, FacebookUserId = mutualFbId });
            dataAccess.Expect(
                x => x.QueryUserRegistry(
                    It.Is<Expression<Func<UserRegistry, bool>>>(
                        q => q.Compile().Invoke(new UserRegistry { UserID = otherId }))))
                .Returns(new UserRegistry { UserID = otherId, FacebookUserId = otherFbId });
            #endregion
            var service_mutual = new Mock<IFacebookService>();
            var service_other = new Mock<IFacebookService>();
            var fbFactory = new Mock<IFacebookServiceFactory>();
            #region fbFactory returns Mocked services
            fbFactory.Expect(x => x
                .Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), mutualFbId))
                .Returns(service_mutual.Object);
            fbFactory.Expect(x => x
                .Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), otherFbId))
                .Returns(service_other.Object);
            #endregion

            Func<string, string, string, bool> textContainsReferencesToUsers = (s, u1, u2) =>
                s.Contains(string.Format(@"<fb:name uid=""{0}""", u1)) &&
                s.Contains(string.Format(@"<fb:name uid=""{0}""", u2));

            service_other.Expect(x => x.UserID).Returns(otherFbId);
            service_other.Expect(x => x.IsValid()).Returns(true);
            service_other.Expect(x => x
                .SendNotification(mutualFbId, 
                It.Is<string>(s => textContainsReferencesToUsers(s, senderFbId, otherFbId)), It.IsAny<string>()))
                .Verifiable();
            service_mutual.Expect(x => x.UserID).Returns(mutualFbId);
            service_mutual.Expect(x => x.IsValid()).Returns(true);
            service_mutual.Expect(x => x
                .SendNotification(otherFbId,
                It.Is<string>(s => textContainsReferencesToUsers(s, senderFbId, mutualFbId)), It.IsAny<string>()))
                .Verifiable();

            var service = new FacebookSocialNetworkService(dataAccess.Object, userService.Object, fbFactory.Object);
            service.SendIntrodutionNotification("foo", senderId, mutualId, otherId);            

            service_mutual.Verify();
            service_other.Verify();
        }
    }
}
